#region Copyright Vanderlande Industries B.V. 2009-2012
//
// Copyright (c) 2009-2012 Vanderlande Industries.
// All rights reserved.
//
// @file:     FscConfiguration.cs
// @version:  %version: 53 %
// %date_created: donderdag 7 maart 2013 11:18:44 %
//
// The copyright to the computer program(s) herein is the property of
// Vanderlande Industries. The program(s) may be used and/or copied
// only with the written permission of the owner or in accordance with
// the terms and conditions stipulated in the contract under which the
// program(s) have been supplied.
//
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using VI.Fsc.Common.Util;
using VI.Fsc.OperatorGui.WebReferences.Opc;
using VI.Fsc.OperatorGui.Models.Application.Util;
using VI.Fsc.OperatorGui.Models.Fsc.Components;
//using VI.Bfw.Logging;

namespace VI.Fsc.OperatorGui.Models.Fsc.Configuration
{
    public enum FscCommands
    {
        COMMAND_NAME_START,
        COMMAND_NAME_STOP,
        COMMAND_NAME_RESET,
        COMMAND_NAME_SETSPEED
    }

    public struct FscCommandInfo
    {
        public string Name;
        public bool ShowSidebarButton;

        public FscCommandInfo(string name, bool showSidebarButton)
        {
            this.Name = name;
            this.ShowSidebarButton = showSidebarButton;
        }
    }

    /// <summary>
    /// Class that holds the settings of a web service: the url and the ping rate
    /// used for calling the service.
    /// </summary>
    public class CFscWebServiceSettings
    {
        private string ipAddress;
        private int pingRate;
        private const string SUPPORTED_PROTOCOL = "http://";

        /// <summary>
        /// Gets/Sets the ip address of the web service. The ip address includes the
        /// protocol, the main ip base, the port separator, the port, and optional and
        /// extra path in which the service is running (usually empty.)
        /// E.g.: http://10.0.0.1:5146/myExtraOptionalDirectory
        /// </summary>
        public string IpAddress
        {
            get
            {
                string ip = ipAddress;

                if (ipAddress.StartsWith(SUPPORTED_PROTOCOL))
                {
                    ip = ipAddress;
                }
                else
                {
                    ip = SUPPORTED_PROTOCOL + OperatorGuiApplicationSettings.Instance.FscIpAddress + ipAddress;
                }

                return ip;
            }
            set
            {
                ipAddress = value;
            }
        }

        public int PingRate
        {
            get { return this.pingRate; }
            set { this.pingRate = value; }
        }

        public CFscWebServiceSettings(string ipAddress, int pingRate)
        {
            this.IpAddress = ipAddress;
            this.PingRate = pingRate;
        }
    }
    
    /// <summary>
    /// The <b>CFscConfiguration</b> class provides access to an fsc configuration values.
    /// </summary>
    public class CFscConfiguration
    {
        protected const string WEB_SERVICE_NAME_USER_EVENTS = "UserEvents";
        protected const string WEB_SERVICE_NAME_OPC = "OPC";

        /// <summary>
        /// The member contains the only possible instance of this class (singleton), 
        /// it is created when it is called the first time.
        /// </summary>
        protected static CFscConfiguration instance;

        /// <summary>
        /// The property returns the instance of the FscConfiguration class, 
        /// if no instance is present the constructor is called.
        /// </summary>
        public static CFscConfiguration Instance
        {
            get
            {
                if (CFscConfiguration.instance == null)
                {
                    CFscConfiguration.instance = new CFscConfiguration();
                }
                return CFscConfiguration.instance;
            }
            set
            {
                CFscConfiguration.instance = value;
            }
        }

        public Dictionary<FscCommands, FscCommandInfo> commands = new Dictionary<FscCommands, FscCommandInfo>();
        protected Dictionary<string, CFscWebServiceSettings> webServicesSettings = new Dictionary<string, CFscWebServiceSettings>();
        protected Dictionary<string, CStructureItem> structureTree;
        protected string areaDescription = null;
        protected List<int> possibleSpeedValues = new List<int>();
        protected List<string> filePrefixes = new List<string>();

        // protected constructor to assure that no public default constructor is available.
        protected CFscConfiguration()
        {
            structureTree = new Dictionary<string, CStructureItem>();
        }

        public CFscWebServiceSettings GetUserEventsWebServiceSettings()
        {
            return webServicesSettings[WEB_SERVICE_NAME_USER_EVENTS];
        }

        public CFscWebServiceSettings GetOpcWebServiceSettings()
        {
            return webServicesSettings[WEB_SERVICE_NAME_OPC];
        }

        /// <summary>
        /// Gets the description for the area.
        /// </summary>
        public string AreaDescription
        {
            get
            {
                return this.areaDescription;
            }
        }

        public Dictionary<string, CStructureItem> StructureTree
        {
            get { return structureTree; }
            set { structureTree = value; }
        }

        private bool loadOpcStructureFromXml = false;
        public bool LoadOpcStructureFromXml
        {
            get { return loadOpcStructureFromXml; }
            set { loadOpcStructureFromXml = value; }
        }

        private bool enableModeButton = false;
        public bool EnableModeButton
        {
            get { return enableModeButton; }
            set { enableModeButton = value; }
        }

        private bool enableDebugFile = false;
        public bool EnableDebugFile
        {
            get { return enableDebugFile; }
            set { enableDebugFile = value; }
        }

        /// <summary>
        /// Gets all possible fsc speeds.
        /// </summary>
        public List<int> PossibleSpeedValues
        {
            get
            {
                return possibleSpeedValues;
            }
        }

        /// <summary>
        /// Gets the path to the start command item.
        /// </summary>
        public string StartCommandPath
        {
            get
            {
                return this.commands[FscCommands.COMMAND_NAME_START].Name;
            }
        }

        /// <summary>
        /// Gets the path to the stop command item.
        /// </summary>
        public string StopCommandPath
        {
            get
            {
                return this.commands[FscCommands.COMMAND_NAME_STOP].Name;
            }
        }

        /// <summary>
        /// Gets the path to the reset command item.
        /// </summary>
        public string ResetCommandPath
        {
            get
            {
                return this.commands[FscCommands.COMMAND_NAME_RESET].Name;
            }
        }

        /// <summary>
        /// Gets the path to the speed command item.
        /// </summary>
        public string SetSpeedCommandPath
        {
            get
            {
                return this.commands[FscCommands.COMMAND_NAME_SETSPEED].Name;
            }
        }

        public string GetSorterMarkcode()
        {
            string sorterMarkcode = string.Empty;
            CStructureItem maoItem = GetFirstItemWithGivenMarkcode(CFscStrings.MARKCODE_MAINTENANCEOBJECT_STRING);

            if (maoItem != null)
            {
                sorterMarkcode = CFscConfiguration.Instance.StructureTree[maoItem.ParentId + "/Info/Markcode"].Value;
            }

            return sorterMarkcode;
        }


        public string GetSorterModePath()
        {
            string sorterModePath = string.Empty;
            CStructureItem maoItem = GetFirstItemWithGivenMarkcode(CFscStrings.MARKCODE_MAINTENANCEOBJECT_STRING);

            if (maoItem != null)
            {
                sorterModePath = maoItem.ParentId + "/Mode";
            }

            return sorterModePath;
        }

        /// <summary>
        /// Set different properties of the configuration.
        /// </summary>
        /// <param name="newAreaDescription">The description for the area.</param>
        /// <param name="opcWebService">The web service for the OPC-server.</param>
        /// <param name="userEventsWebService">The web service for the user-events-server.</param>
        /// <param name="commands">Commands that can be sent to the server.</param>
        internal void Set(string newAreaDescription, CFscWebServiceSettings opcWebService,
            CFscWebServiceSettings userEventsWebService, Dictionary<FscCommands, FscCommandInfo> commands)
        {
            this.areaDescription = newAreaDescription;
            this.webServicesSettings.Add(WEB_SERVICE_NAME_OPC, opcWebService);
            this.webServicesSettings.Add(WEB_SERVICE_NAME_USER_EVENTS, userEventsWebService);
            this.commands = commands;
        }

        /// <summary>
        /// Clears the current configuration data structure, if any.
        /// </summary>
        public void ClearConfiguration()
        {
            this.StructureTree.Clear();
        }

        /// <summary>
        /// Gets all the errors and warnings combined into a list.
        /// </summary>
        /// <returns> The list containing the errors first, followed by the warnings. Each part is ordered by id.
        /// </returns>
        public List<CStructureItem> GetOrderedErrorsAndWarnings()
        {
            //Logger.Entering(MethodBase.GetCurrentMethod());

            List<CStructureItem> allErrors = new List<CStructureItem>();
            List<CStructureItem> allWarnings = new List<CStructureItem>();
            List<CStructureItem> completeList = new List<CStructureItem>();

            // First get all errors in a list, all warnings in another list.
            foreach (KeyValuePair<string, CStructureItem> item in StructureTree)
            {
                int problemKey;
                int.TryParse(item.Value.ProblemKey, out problemKey);
                if (problemKey > 0)
                {
                    switch (item.Value.Status)
                    {
                        case ComponentStatus.ERROR:
                                allErrors.Add(item.Value);
                            break;

                        case ComponentStatus.WARNING:
                                allWarnings.Add(item.Value);
                            break;

                        default:
                            break;
                    }
                }
            }

            allErrors.Sort(CStructureItem.CompareItemsById);
            allWarnings.Sort(CStructureItem.CompareItemsById);

            completeList = allErrors;
            completeList.AddRange(allWarnings);

            //Logger.Exiting(MethodBase.GetCurrentMethod(), "Found " + allErrors.Count + " errors and " +
                //allWarnings.Count + " warnings.");
            return completeList;
        }

        public SubscribeRequestItemList GetSubscribeRequestItemList()
        {
            SubscribeRequestItemList list = new SubscribeRequestItemList();
            List<SubscribeRequestItem> tempList = new List<SubscribeRequestItem>();
            
            AddAdditionalItems(tempList);

            foreach (KeyValuePair<string, CStructureItem> item in this.StructureTree)
            {
                if (item.Key.EndsWith(CFscStrings.RSDETAILS_STRING) ||
                    item.Key.EndsWith(CFscStrings.RSTATE_STRING) ||
                    item.Key.EndsWith(CFscStrings.COMMAND_SETMODE) ||
                    item.Key.EndsWith(CFscStrings.MODE_STRING_WITH_SLASH))
                {
                    if ( item.Key.EndsWith(CFscStrings.COMMAND_SETMODE))
                    {
                        //Logger.Info(MethodBase.GetCurrentMethod(), "Registering on SetMode = " + item.Key);
	                }
                    tempList.Add(CFscUtility.CreateNewSubscribeRequestItem(item.Key));
                }
            }

            list.Items = tempList.ToArray();

            return list;
        }

        /// <summary>
        /// Left empty, can be used/overridden in specialised classes
        /// </summary>
        /// <param name="list">List to add items to.</param>
        protected virtual void AddAdditionalItems(List<SubscribeRequestItem> list)
        {
            //empty
        }

        /// <summary>
        /// Get all elements from the OPC tree that own the given markcode and do have an "RState" item.
        /// </summary>
        /// <param name="markcode">The markcode to look for.</param>
        /// <returns>Dictionary of elements with given markcode as the value and the shortId as the key. 
        /// Returns an empty list, if markcode wasn't found.</returns>
        public Dictionary<string, CMaintenanceDisplayItem> GetStatusItemsByMarkcode(string markcode)
        {
            Dictionary<string, CMaintenanceDisplayItem> retVal = new Dictionary<string, CMaintenanceDisplayItem>();

            foreach (KeyValuePair<string, CStructureItem> item in this.StructureTree)
            {
                // Check for the correct markcode. The markcode is in the CStructureItem.Value field.
                if (item.Key.EndsWith(CFscStrings.MARKCODE_STRING) &&                    
                    (!string.IsNullOrEmpty(item.Value.Value) &&
                    item.Value.Value.Contains(markcode)))
                {
                    //we want the key as the shortId of the item node (e.g. CRR23)
                    string shortId = StructureTree[item.Key.Substring(0, item.Key.LastIndexOf(CFscStrings.INFO_STRING) - 1)].ShortId;
                    string longId = StructureTree[item.Key.Substring(0, item.Key.LastIndexOf(CFscStrings.INFO_STRING) - 1)].Id;

                    //the item is the StructureITem which owns the status in the value field
                    CStructureItem theRStateItem = null;
                    StructureTree.TryGetValue(item.Key.Substring(0, item.Key.LastIndexOf(CFscStrings.INFO_STRING)) + CFscStrings.RSTATE_STRING, out theRStateItem);
                    CStructureItem theRsDetailsItem = null;
                    StructureTree.TryGetValue(item.Key.Substring(0, item.Key.LastIndexOf(CFscStrings.INFO_STRING)) + CFscStrings.RSDETAILS_STRING, out theRsDetailsItem);
                    CStructureItem theModeItem = null;
                    CStructureItem theOfflineItem = null;

                    if (markcode.Equals(CFscStrings.MARKCODE_CARRIER_STRING))
                    {
                        theModeItem = StructureTree[item.Key.Substring(0, item.Key.LastIndexOf(CFscStrings.INFO_STRING)) + CFscStrings.MODE_STRING_WITHOUT_SLASH];
                        theOfflineItem = StructureTree[item.Key.Substring(0, item.Key.LastIndexOf(CFscStrings.INFO_STRING)) + "Settings/Offline"];
                    }
                    else if (markcode.Equals(CFscStrings.MARKCODE_LDC_STRING))
                    {
                        theOfflineItem = StructureTree[item.Key.Substring(0, item.Key.LastIndexOf(CFscStrings.INFO_STRING)) + CFscStrings.SETTINGS_REQUESTOFF];
                    }

                    // Check whether the Rstate item and RsDetails item exist, because not all items have these.
                    CMaintenanceDisplayItem displayItem = new CMaintenanceDisplayItem(
                        markcode, shortId, longId, 
                        (theRStateItem == null) ? string.Empty : theRStateItem.Value,
                        (theRsDetailsItem == null) ? string.Empty : theRsDetailsItem.Value);
                    if (theModeItem != null && theModeItem.Value != null)
                    {
                        displayItem.Mode = theModeItem.Value;
                    }
                    if (theOfflineItem != null && theOfflineItem.Value != null)
                    {
                        displayItem.IsOffline = Convert.ToInt16(theOfflineItem.Value);
                    }
                    retVal.Add(longId.Substring(longId.LastIndexOf("-") + 1), displayItem);
                }
            }

            return retVal;
        }

        /// <summary>
        /// Get all elements from the OPC tree that own the given markcode.
        /// </summary>
        /// <remarks>Returns an empty list, if markcode wasn't found.</remarks>
        /// <param name="markcode">The markcode to look for.</param>
        /// <returns>List of elements with given markcode</returns>
        public List<CStructureItem> GetItemsByMarkcode(string markcode)
        {
            List<CStructureItem> retVal = new List<CStructureItem>();
            foreach (KeyValuePair<string, CStructureItem> item in this.StructureTree)
            {
                if (item.Key.EndsWith(CFscStrings.MARKCODE_STRING) &&
                    item.Key.LastIndexOf(markcode, StringComparison.InvariantCultureIgnoreCase) > 0)
                {
                    //we want the parent node (e.g. CRR23)
                    CStructureItem theItem = StructureTree[item.Key.Substring(0, item.Key.LastIndexOf("Info")-1)];
                    retVal.Add(theItem);
                }
            }

            return retVal;
        }

        /// <summary>
        /// Get first element from the OPC tree that own the given markcode.
        /// </summary>
        /// <remarks>Returns null, if markcode wasn't found.</remarks>
        /// <param name="markcode">The markcode to look for.</param>
        /// <returns>element with given markcode</returns>
        public CStructureItem GetFirstItemWithGivenMarkcode(string markcode)
        {
            CStructureItem retVal = null;
            foreach (KeyValuePair<string, CStructureItem> item in this.StructureTree)
            {
                if (item.Key.EndsWith(CFscStrings.MARKCODE_STRING) &&
                    item.Key.LastIndexOf(markcode, StringComparison.InvariantCultureIgnoreCase) > 0)
                {
                    //we want the parent node (e.g. CRR23)
                    retVal = StructureTree[item.Key.Substring(0, item.Key.LastIndexOf("Info") - 1)];
                    break;
                }
            }

            return retVal;
        }

        /// <summary>
        /// Get all elements from the OPC tree that own a markcode having the given markcode value.
        /// </summary>
        /// <param name="markcode">The markcode to look for.</param>
        /// <returns>List of elements with given markcode, empty list if none was found.</returns>
        public List<CStructureItem> GetItemsByMarkcodeValue(string markcode)
        {
            //Logger.Entering(MethodBase.GetCurrentMethod(), "markcode = " + markcode);

            List<CStructureItem> items = new List<CStructureItem>();
            foreach (KeyValuePair<string, CStructureItem> item in this.StructureTree)
            {
                if (item.Key.EndsWith(CFscStrings.MARKCODE_STRING) && item.Value.Value.Equals(markcode, StringComparison.InvariantCultureIgnoreCase))
                {
                    //Logger.Debug(MethodBase.GetCurrentMethod(), "Found item with key = " + item.Key + " and value = " + item.Value.Value);
                    // Get the parent node (e.g. CRR23).
                    CStructureItem theItem = StructureTree[item.Key.Substring(0, item.Key.LastIndexOf("Info") - 1)];
                    items.Add(theItem);
                }
            }

            //Logger.Exiting(MethodBase.GetCurrentMethod());
            return items;
        }

        /// <summary>
        /// Get all elements from the OPC tree that own the given markcode and functionVariant.
        /// </summary>
        /// <remarks>Returns an empty list, if markcode wasn't found.</remarks>
        /// <param name="markcode">The markcode to look for.</param>
        /// <returns>List of elements with given markcode</returns>
        public CStructureItem GetItemsByMarkcodeAndFunctionVariant(string markcode, string functionVariant)
        {
            CStructureItem retVal = null;
            foreach (CStructureItem item in GetItemsByMarkcode(markcode))
            {
                if (!string.IsNullOrEmpty(item.FunctionVariant) &&
                    item.FunctionVariant == functionVariant)
                {
                    retVal = item;
                }
            }

            return retVal;
        }

        /// <summary>
        /// Get the element from the OPC tree that owns the given shortId.
        /// </summary>
        /// <remarks>Returns an empty list, if markcode wasn't found.</remarks>
        /// <param name="shortId">The shortId to look for. If this is null or empty the method will return null.</param>
        /// <returns>List of elements with given shortId</returns>
        public CStructureItem GetItemByShortId(string shortId)
        {
            CStructureItem retVal = null;

            if (!String.IsNullOrEmpty(shortId))
            {
                foreach (KeyValuePair<string, CStructureItem> item in this.StructureTree)
                {
                    if (item.Key.EndsWith(CFscStrings.MARKCODE_STRING) &&
                        item.Key.LastIndexOf(shortId, StringComparison.InvariantCultureIgnoreCase) > 0)
                    {
                        //we want the parent node (e.g. CRR23)
                        retVal = StructureTree[item.Key.Substring(0, item.Key.LastIndexOf("Info") - 1)];
                        break;
                    }
                }
            }

            return retVal;
        }

        /// <summary>
        /// Get the element from the OPC tree that ends with the given path.
        /// </summary>
        /// <remarks>Returns an empty list, if relativePath wasn't found.</remarks>
        /// <param name="relativePath">The part of the path to look for. If this is null or empty the method will return null.</param>
        /// <returns>List of elements with given relativePath</returns>
        public CStructureItem GetItemByRelativePath(string relativePath)
        {
            CStructureItem retVal = null;

            if (!String.IsNullOrEmpty(relativePath))
            {
                foreach (KeyValuePair<string, CStructureItem> item in this.StructureTree)
                {
                    if (item.Key.EndsWith(relativePath))
                    {
                        retVal = item.Value;
                        break;
                    }
                }
            }

            return retVal;
        }

        /// <summary>
        /// Returns the root element in the structure tree that contains a pstate.
        /// </summary>
        /// <returns></returns>
        public CStructureItem GetRootWithPState()
        {
            CStructureItem root = null;
            
            foreach (CStructureItem item in structureTree.Values)
            {
                if (String.IsNullOrEmpty(item.ParentId))
                {
                    if (structureTree.ContainsKey(item.Id + "/PState"))
                    {
                        root = structureTree[item.Id];
                    }
                }
            }
            
            return root;
        }

        public IEnumerable<string> GetAvailableCommandsOfComponent(string component)
        {
            return new StructureTree(structureTree).GetAvailableCommandsOfComponent(component);
        }

        public KeyValuePair<string, CStructureItem> GetArea()
        {
            return structureTree
                .Single(x => x.Value.IntegrationLevel == CFscStrings.INTEGRATIONLEVEL_AREA);
        }
    }
}